/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/Blend.h"

#include "include/core/SkBlendMode.h"

#ifdef SK_DEBUG
#include "include/core/SkString.h"
#endif

namespace skgpu {
const char *BlendFuncName(SkBlendMode mode)
{
    switch (mode) {
        case SkBlendMode::kClear:
            return "blend_clear";
        case SkBlendMode::kSrc:
            return "blend_src";
        case SkBlendMode::kDst:
            return "blend_dst";
        case SkBlendMode::kSrcOver:
            return "blend_src_over";
        case SkBlendMode::kDstOver:
            return "blend_dst_over";
        case SkBlendMode::kSrcIn:
            return "blend_src_in";
        case SkBlendMode::kDstIn:
            return "blend_dst_in";
        case SkBlendMode::kSrcOut:
            return "blend_src_out";
        case SkBlendMode::kDstOut:
            return "blend_dst_out";
        case SkBlendMode::kSrcATop:
            return "blend_src_atop";
        case SkBlendMode::kDstATop:
            return "blend_dst_atop";
        case SkBlendMode::kXor:
            return "blend_xor";
        case SkBlendMode::kPlus:
            return "blend_plus";
        case SkBlendMode::kModulate:
            return "blend_modulate";
        case SkBlendMode::kScreen:
            return "blend_screen";
        case SkBlendMode::kOverlay:
            return "blend_overlay";
        case SkBlendMode::kDarken:
            return "blend_darken";
        case SkBlendMode::kLighten:
            return "blend_lighten";
        case SkBlendMode::kColorDodge:
            return "blend_color_dodge";
        case SkBlendMode::kColorBurn:
            return "blend_color_burn";
        case SkBlendMode::kHardLight:
            return "blend_hard_light";
        case SkBlendMode::kSoftLight:
            return "blend_soft_light";
        case SkBlendMode::kDifference:
            return "blend_difference";
        case SkBlendMode::kExclusion:
            return "blend_exclusion";
        case SkBlendMode::kMultiply:
            return "blend_multiply";
        case SkBlendMode::kHue:
            return "blend_hue";
        case SkBlendMode::kSaturation:
            return "blend_saturation";
        case SkBlendMode::kColor:
            return "blend_color";
        case SkBlendMode::kLuminosity:
            return "blend_luminosity";
    }
    SkUNREACHABLE;
}

SkSpan<const float> GetPorterDuffBlendConstants(SkBlendMode mode)
{
    static constexpr float kClear[]      = {0, 0,  0,  0};
    static constexpr float kSrc[]        = {1, 0,  0,  0};
    static constexpr float kDst[]        = {0, 1,  0,  0};
    static constexpr float kSrcOver[]    = {1, 0,  0, -1};
    static constexpr float kDstOver[]    = {0, 1, -1,  0};
    static constexpr float kSrcIn[]      = {0, 0,  1,  0};
    static constexpr float kDstIn[]      = {0, 0,  0,  1};
    static constexpr float kSrcOut[]     = {0, 0, -1,  0};
    static constexpr float kDstOut[]     = {0, 0,  0, -1};
    static constexpr float kSrcATop[]    = {0, 0,  1, -1};
    static constexpr float kDstATop[]    = {0, 0, -1,  1};
    static constexpr float kXor[]        = {0, 0, -1, -1};
    static constexpr float kPlus[]       = {1, 1,  0,  0};

    switch (mode) {
        case SkBlendMode::kClear:
            return SkSpan(kClear);
        case SkBlendMode::kSrc:
            return SkSpan(kSrc);
        case SkBlendMode::kDst:
            return SkSpan(kDst);
        case SkBlendMode::kSrcOver:
            return SkSpan(kSrcOver);
        case SkBlendMode::kDstOver:
            return SkSpan(kDstOver);
        case SkBlendMode::kSrcIn:
            return SkSpan(kSrcIn);
        case SkBlendMode::kDstIn:
            return SkSpan(kDstIn);
        case SkBlendMode::kSrcOut:
            return SkSpan(kSrcOut);
        case SkBlendMode::kDstOut:
            return SkSpan(kDstOut);
        case SkBlendMode::kSrcATop:
            return SkSpan(kSrcATop);
        case SkBlendMode::kDstATop:
            return SkSpan(kDstATop);
        case SkBlendMode::kXor:
            return SkSpan(kXor);
        case SkBlendMode::kPlus:
            return SkSpan(kPlus);
        default:
            return {};
    }
}

ReducedBlendModeInfo GetReducedBlendModeInfo(SkBlendMode mode)
{
    static constexpr float kHue[]        = {0, 1};
    static constexpr float kSaturation[] = {1, 1};
    static constexpr float kColor[]      = {0, 0};
    static constexpr float kLuminosity[] = {1, 0};

    static constexpr float kOverlay[]    = {0};
    static constexpr float kHardLight[]  = {1};

    static constexpr float kDarken[]     = {1};
    static constexpr float kLighten[]    = {-1};

    switch (mode) {
            // Clear/src/dst are intentionally omitted; using the built-in blend_xxxxx functions is
            // preferable, since that gives us an opportunity to eliminate the src/dst entirely.

        case SkBlendMode::kSrcOver:
        case SkBlendMode::kDstOver:
        case SkBlendMode::kSrcIn:
        case SkBlendMode::kDstIn:
        case SkBlendMode::kSrcOut:
        case SkBlendMode::kDstOut:
        case SkBlendMode::kSrcATop:
        case SkBlendMode::kDstATop:
        case SkBlendMode::kXor:
        case SkBlendMode::kPlus:
            return { "blend_porter_duff", GetPorterDuffBlendConstants(mode) };

        case SkBlendMode::kHue:
            return { "blend_hslc", SkSpan(kHue) };
        case SkBlendMode::kSaturation:
            return { "blend_hslc", SkSpan(kSaturation) };
        case SkBlendMode::kColor:
            return { "blend_hslc", SkSpan(kColor) };
        case SkBlendMode::kLuminosity:
            return { "blend_hslc", SkSpan(kLuminosity) };

        case SkBlendMode::kOverlay:
            return { "blend_overlay", SkSpan(kOverlay) };
        case SkBlendMode::kHardLight:
            return { "blend_overlay", SkSpan(kHardLight) };

        case SkBlendMode::kDarken:
            return { "blend_darken", SkSpan(kDarken) };
        case SkBlendMode::kLighten:
            return { "blend_darken", SkSpan(kLighten) };

        default:
            return { BlendFuncName(mode), {} };
    }
}

#ifdef SK_DEBUG

namespace {
const char *equation_string(skgpu::BlendEquation eq)
{
    switch (eq) {
        case skgpu::BlendEquation::kAdd:
            return "add";
        case skgpu::BlendEquation::kSubtract:
            return "subtract";
        case skgpu::BlendEquation::kReverseSubtract:
            return "reverse_subtract";
        case skgpu::BlendEquation::kScreen:
            return "screen";
        case skgpu::BlendEquation::kOverlay:
            return "overlay";
        case skgpu::BlendEquation::kDarken:
            return "darken";
        case skgpu::BlendEquation::kLighten:
            return "lighten";
        case skgpu::BlendEquation::kColorDodge:
            return "color_dodge";
        case skgpu::BlendEquation::kColorBurn:
            return "color_burn";
        case skgpu::BlendEquation::kHardLight:
            return "hard_light";
        case skgpu::BlendEquation::kSoftLight:
            return "soft_light";
        case skgpu::BlendEquation::kDifference:
            return "difference";
        case skgpu::BlendEquation::kExclusion:
            return "exclusion";
        case skgpu::BlendEquation::kMultiply:
            return "multiply";
        case skgpu::BlendEquation::kHSLHue:
            return "hsl_hue";
        case skgpu::BlendEquation::kHSLSaturation:
            return "hsl_saturation";
        case skgpu::BlendEquation::kHSLColor:
            return "hsl_color";
        case skgpu::BlendEquation::kHSLLuminosity:
            return "hsl_luminosity";
        case skgpu::BlendEquation::kIllegal:
            SkASSERT(false);
            return "<illegal>";
    }

    SkUNREACHABLE;
}

const char *coeff_string(skgpu::BlendCoeff coeff)
{
    switch (coeff) {
        case skgpu::BlendCoeff::kZero:
            return "zero";
        case skgpu::BlendCoeff::kOne:
            return "one";
        case skgpu::BlendCoeff::kSC:
            return "src_color";
        case skgpu::BlendCoeff::kISC:
            return "inv_src_color";
        case skgpu::BlendCoeff::kDC:
            return "dst_color";
        case skgpu::BlendCoeff::kIDC:
            return "inv_dst_color";
        case skgpu::BlendCoeff::kSA:
            return "src_alpha";
        case skgpu::BlendCoeff::kISA:
            return "inv_src_alpha";
        case skgpu::BlendCoeff::kDA:
            return "dst_alpha";
        case skgpu::BlendCoeff::kIDA:
            return "inv_dst_alpha";
        case skgpu::BlendCoeff::kConstC:
            return "const_color";
        case skgpu::BlendCoeff::kIConstC:
            return "inv_const_color";
        case skgpu::BlendCoeff::kS2C:
            return "src2_color";
        case skgpu::BlendCoeff::kIS2C:
            return "inv_src2_color";
        case skgpu::BlendCoeff::kS2A:
            return "src2_alpha";
        case skgpu::BlendCoeff::kIS2A:
            return "inv_src2_alpha";
        case skgpu::BlendCoeff::kIllegal:
            SkASSERT(false);
            return "<illegal>";
    }

    SkUNREACHABLE;
}
} // anonymous namespace

SkString BlendInfo::dump() const
{
    SkString out;
    out.printf("writes_color(%d) equation(%s) src_coeff(%s) dst_coeff:(%s) const(0x%08x)", fWritesColor,
        equation_string(fEquation), coeff_string(fSrcBlend), coeff_string(fDstBlend), fBlendConstant.toBytes_RGBA());
    return out;
}

#endif // SK_DEBUG
} // namespace skgpu
